
import { jobGoto } from "../../../../job/goto";
import { jobHeal } from "../../../../job/heal";
import { jobRangedAttack } from "../../../../job/ranged_attack";
import { getMaxEnergy, spawnCreep } from "../../../../role/spawn";
import { errLog } from "../../../../util/log";
import { Group, getCreepNames } from "../../../group";

export class GroupQuadAttacker extends Group{
    constructor(spawnRoom = undefined, targetRoomName = undefined){
        super(spawnRoom);
        if(spawnRoom !== undefined){
            this.targetRoomName = targetRoomName;
            this.attackerNames = [];
            this.healerNames = [];
            this.spawning = true;
            this.memory = {};
        }
    }

    get attackers(){
        if(this._attackers !== undefined){
            return this._attackers;
        }

        let attackers = [];
        for(let name of this.attackerNames){
            attackers.push(Game.creeps[name]);
        }
        return this._attackers = attackers;
    }
    get healers(){
        if(this._healers !== undefined){
            return this._healers;
        }

        let healers = [];
        for(let name of this.healerNames){
            healers.push(Game.creeps[name]);
        }
        return this._healers = healers;

    }

    serialize(){
        return {
            super: super.serialize(),
            attackerNames: this.attackerNames,
            healerNames: this.healerNames,
            spawning: this.spawning,
            targetRoomName: this.targetRoomName,
            memory: this.memory,
        };
    }
    deserialize(obj){
        super.deserialize(obj.super);
        this.spawning = obj.spawning;
        this.attackerNames = getCreepNames(obj.attackerNames);
        this.healerNames = getCreepNames(obj.healerNames);
        this.targetRoomName = obj.targetRoomName;
        this.memory = obj.memory;
        return this;
    }
    get size(){
        return this.attackerNames.length+this.healerNames.length;
    }
    get maxSize(){
        if(this.spawning){
            return 4;
        }
        return this.size;
    }
    get targetRoom(){
        return Game.rooms[this.targetRoomName];
    }
    get targets(){
        if(this._targets !== undefined){
            return this._targets;
        }

        let targetRoom = this.targetRoom;
        if(!targetRoom){
            return this._targets = [new RoomPosition(25,25,this.targetRoomName)];
        }

        let hostiles = targetRoom.find(FIND_HOSTILE_STRUCTURES, {
            filter(structure){
                return !(structure instanceof StructureController);
            }
        });
        hostiles.push(...targetRoom.find(FIND_HOSTILE_CREEPS));
        if(this.attackers.length > 0){
            hostiles = jobGoto.sortTargetsByDistance(this.attackers[0], hostiles);
        }
        return this._targets = hostiles;
    }
    isArrived0(){
        if(!this.targetRoom){
            return false;
        }
        for(let creep of this.attackers){
            if(creep.room.name !== this.targetRoomName){
                return false;
            }
        }
        for(let creep of this.healers){
            if(creep.room.name !== this.targetRoomName){
                return false;
            }
        }
        return true;
    }
    get arrived(){
        if(this._arrived !== undefined){
            return this._arrived;
        }

        return this._arrived = this.isArrived0();
    }
    spawnAttacker(){
        let newCreep = spawnCreep(this.spawnRoom, {
            require: [MOVE,RANGED_ATTACK],
            move: .34,
            ranged_attack: .66,
        },{
            memory: {
                role: "attacker",
            }
        }, getMaxEnergy(this.spawnRoom));
        if(!(newCreep instanceof Creep)){
            return newCreep;
        }
        this.attackerNames.push(newCreep.name);
        return OK;
    }
    spawnHealer(){
        let newCreep = spawnCreep(this.spawnRoom, {
            require: [MOVE,HEAL],
            move: .34,
            heal: .66,
        },{
            memory: {
                role: "healer",
            }
        }, getMaxEnergy(this.spawnRoom));
        if(!(newCreep instanceof Creep)){
            return newCreep;
        }
        this.healerNames.push(newCreep.name);
        return OK;
    }
    spawn(){
        if(!this.spawning){
            return ERR_FULL;
        }
        if(this.healers.length===0){
            return this.spawnHealer();
        }

        if(this.attackers.length<this.healers.length){
            return this.spawnAttacker();
        }

        if(this.healers.length<2){
            return this.spawnHealer();
        }
        return ERR_FULL;
    }
    getDamagedCreeps(){
        if(this._damagedCreeps !== undefined){
            return this._damagedCreeps;
        }

        let targets = this.attackers.filter((target)=>target.hits<target.hitsMax);
        targets.push(...this.healers.filter((target)=>target.hits<target.hitsMax));

        targets.sort((a,b)=>a.hits/a.hitsMax-b.hits/b.hitsMax);

        return this._damagedCreeps = targets;
    }
    run(){
        if(this.spawning){
            for(let creep of this.attackers){
                try{
                    this.renew(creep);
                }catch(e){
                    errLog(e);
                }
            }
            for(let creep of this.healers){
                try{
                    this.renew(creep);
                }catch(e){
                    errLog(e);
                }
            }
            return OK;
        }
        
        if(this.size>=this.maxSize){
            this.clusterRun(...this.attacker, ...this.healers);
        }
        for(let attacker of this.attackers){
            this.attackerRun(attacker);
        }
        for(let i=0; i<this.healers; i++){
            this.healerRun(this.healers[i],i);
        }
    }
    /**
     * 
     * @param {AnyCreep} creep 
     * @returns {CreepActionReturnCode}
     */
    renew(creep){
        if(creep.ticksToLive>1400){
            return OK;
        }
        let spawns = this.spawnRoom.find(FIND_MY_SPAWNS);
        let nearest = jobGoto.getClosest(creep, spawns);
        let ret;
        if(!nearest.pos.isNearTo(creep.pos)){
            ret = jobGoto.run(creep, [nearest]);
        }else{
            ret = nearest.renewCreep(creep);
        }
        creep.say("renew:"+ret);
        return ret;
    }
    /**
     * 
     * @param {AnyCreep} creep 
     */
    attackerRun(creep){
        return jobRangedAttack.run(creep, this.targets, {visualizePathStyle: {stroke: "#ff0000"}});
    }
    /**
     * 
     * @param {AnyCreep} creep 
     */
    healerRun(creep){
        let targets = this.getDamagedCreeps();
        // let near = false;
        // for(let target of this.attackers){
        //     if(creep.pos.isNearTo(target.pos)){
        //         near = true;
        //         break;
        //     }
        // }
        return jobHeal.run(creep, targets);
    }

    getCreeps(){
        return [this.attackers[0], this.attackers[1], this.healers[0], this.healers[1]];
    }
    
    isCenterized(a1,a2,h1,h2){
        if(this._isCenterized !== undefined){
            return this._isCenterized;
        }
        this._isCenterized = true;
        try{
            assert(a1.pos.isNearTo(a2.pos));
            assert(a2.pos.isNearTo(h1.pos));
            assert(h1.pos.isNearTo(h2.pos));
            assert(h2.pos.isNearTo(a1.pos));
            assert(h1.pos.isNearTo(a1.pos));
            assert(h2.pos.isNearTo(a2.pos));
        }catch(e){
            this._isCenterized = false;
        }
        return this._isCenterized;
    }
    /**
     * 
     * @param {RoomPosition} pos 
     * @param {DirectionConstant} direction 
     * @returns {RoomPosition}
     */
    step(pos, direction){
        let {x,y,roomName}=pos;
        let offset;
        switch(direction){
            case TOP:
                offset = [ 0,-1];
                break;
            case TOP_RIGHT:
                offset = [ 1,-1];
                break;
            case RIGHT:
                offset = [ 1, 0];
                break;
            case BOTTOM_RIGHT:
                offset = [ 1, 1];
                break;
            case BOTTOM:
                offset = [ 0, 1];
                break;
            case BOTTOM_LEFT:
                offset = [-1, 1];
                break;
            case LEFT:
                offset = [-1, 0];
                break;
            case TOP_LEFT:
                offset = [-1,-1];
                break;
            default:
                offset = [ 0, 0];
                break;
        }
        x+=offset[0];
        y+=offset[1];
        return new RoomPosition(x,y,roomName);
    }
    /**
     * @param {DirectionConstant} direction
     */
    move(direction){
        let united = this.size === 4 && this.isCenterized(...this.getCreeps());
        if(united){
            let terrain = this.targetRoom.getTerrain();
            let blocked = false;
            for(let creep of this.getCreeps()){
                if(terrain.get(this.step(creep.pos, direction)) === TERRAIN_MASK_WALL){
                    blocked = true;
                    break;
                }
            }
            if(!blocked){
                for(let creep of this.getCreeps()){
                    creep.move(direction);
                }
                return OK;
            }
        }
        return ERR_NO_PATH;
    }
    unite(){

    }
    /**
     * 
     * @param {AnyCreep} creep 
     * @param {AnyCreep} target 
     */
    creepFollow(creep, target){
        if(creep.pos.isNearTo(target)){
            return creep.move(target);
        }
        let distance = creep.pos.getRangeTo(target);
        if(distance < 10){
            return creep.move(creep.pos.getDirectionTo(target));
        }
        return creep.moveTo(target, {reusePath: distance*1.5});
    }

    // getCenter(a1,a2,h1,h2){
    //     let creeps = [a1,a2,h1,h2].filter((creep)=>creep.room.name === this.targetRoomName);
    //     if(creeps.length === 0){
    //         return this.targetRoom.getPositionAt(24,24);
    //     }
    //     return 
    // }
    /**
     * 
     * @param {Creep} a1 
     * @param {Creep} a2 
     * @param {Creep} h1 
     * @param {Creep} h2 
     */
    clusterRun(a1,a2,h1,h2){
        let creeps = [];
        creeps.push(...arguments);

        if(!this.arrived){
            this.creepFollow(a2, a1);
            this.creepFollow(h1, a1);
            this.creepFollow(h2, a2);

            jobGoto.run(a1, this.targets);
            return OK;
        }

        if(!this.isCenterized(...creeps)){
            return this.unite();
        }


    }
}
function assert(obj){
    if(!obj){
        throw new Error();
    }
}